STM8 Timer 2 - Zeitbasis


Die Nutzung eines Timers als primäre Zeitsteuerung eines Betriebssystems oder Kernels oder im einfachsten Fall für eine zeitliche Ablaufsteuerung innerhalb einer Anwendung, ist eine übliche Aufgabe. Diese kurze Beschreibung soll einen einfachen Überblick geben wie vorzugehen ist um dieses Ziel mit einem STM8 und dessen Timer 2 zu erreichen.

Mit Hilfe des Timer 2 (natürlich kann auch jeder andere Timer auf dem STM8 verwendet werden) kann eine Zeitbasis für eine STM8 Software eingerichtet werden. Als Schnittstellen für die Anwendung sollen die folgenden globalen Variablen bereitstellt werden die von der S/W-Anwendung abgefragt werden können:

  • TFLAG ist eine Variable die alle 100 ms inkrementiert wird bis der Wert 10 ereicht ist worauf das SECFLAG gesetzt (incrementiert) wird. Dies geschieht innerhalb der Interrupt Subroutine des TIMER2 Interrupts.
  • SECFLAG ist eine Variable die ebenfalls von der Interrupt Subroutine des TIMER2 Interrupts jede Sekunde inkrementiert wird und nach der Verwendung von der Applikation zurückgesetzt werden soll.
  • BITTEST ist eine Hilfsvariable die in diesem Beispiel verwendet wird um die LED am Port D, Bit 0, ein- und auszuschalten (z.B. für das STM8 Discovery Board).
  • Alle Variablen werden wegen des schnellen und einfachen Zugriffs im RAM0 angelegt wo sie mit Befehlen mit einer nur 1 Byte langen Adresse angesprochen werden können. Dies ist aber keine zwingende Vorgabe da auch Variablen im „normalen“ RAM-Bereich (`ram1´) möglich sind.

    ;--------------------------------------------------------------------------------------------------------------------------------------------------
				
	segment 'ram0'		; Direktive, die nachfolgenden Variablen im ´ram0` anzulegen
;-------------------------------------------------
				-------------------------------------------------------------------------------------------------	

				TFLAG		ds.b		; Timer 2 Flag 	(wird über Interrupt alle 100 ms gesetzt)

				SECFLAG		ds.b		; Second Flag 	(wird über Interrupt jede 1 Sekunde gesetzt)

				BITTEST		ds.b		; Toggle Bit 	(als Hilfsvariable in diesem Beispiel um die LED zu togglen)

     Festlegen der Taktfrequenz (fMASTER)

    Für die weitere Initialisierungen des Timers wird von einem 16 MHz Takt für fMASTER ausgegangen, der entweder vom HSI (High Speed Internal RC Oszillator) oder vom HSE (High Speed External Quarz Oszillator) bereit gestellt wird.

    Benutzung des HSE Oszillators

    Folgende Initialisierungs-Sequenz wird nur für den HSE mit externem Quarz benötigt!

    ;*****************************************************************************************

				;    Initialisiere den CLOCK Oscillator HSE (16 MHz Crystal)     
				;*****************************************************************************************
				;	
					bset	CLK_SWCR,#1		; Setze das "Switch Enable Bit" (SWEN) in CLK_SWCR 
					
											; Freigabe für die Umschaltung der Taktquelle
				;
					bset	CLK_ECKR,#1		; Setze "HSE Enable Bit" (HSEEN) in CLK_ECKR (Ext. Clock Register)

											; HSE Clock wird aktiviert.

					ld		A,#$B4			; Lade den “Selektor Wert” 0xB4 in den Accumulator 
					
											; 0xB4 = HSE als Taktquelle ausgewählt

					ld 		CLK_SWR,A		; Lade den “Master Clock Selector Wert” in CLK_SWR

											; Ab diesem Zeitpunkt wird auf den externen Takt umgeschaltet.

    Alternativ kann auch der HSI als Taktquelle verwendet werden. Dazu wird die nachfolgende Sequenz statt der HSE Sequenz in die Initialisierung eingefügt.

    Benutzung des HSI Oszillators

    Der HSI ist sozusagen der Standard-Oszillator im STM8, der nach einem Reset automatisch aktiviert wird. Durch den „High Speed Internal Clock Prescaler“ im Register CLK_CKDIVR wird dessen Frequenz nach einem Reset aber durch 128 geteilt (125 kHz). Daher muß der Prescaler auf den Teiler-Faktor 1 umgestellt werden.

    ;******************************************************************************************

				;      Switch internal HSI clock to 16 MHz

				;******************************************************************************************

					ld	A,#$E8			; Lade die Maske für HSIDIV[1..0], Bits[4..3] in CLK_CKDIVR

										; Maske:   0x1110.0111b

					and	A,CLK_CKDIVR	; "AND" Clock Divider Register mit Maske

										; Durch bitweises “UND” werden die Bits[4..3] auf “0” gesetzt

					ld	CLK_CKDIVR,A	; Lade CLK_CKDIVR mit HSIDIV auf 0x00b gesetzt zurück in 

										; das CLK_CKDIVR Register

    Mit dieser Einstellung wird die HSI Frequenz durch den Faktor 1 geteilt und liefert die gewünschten 16 MHz an den Vorteiler des Timer 2.

     Einstellung des Timer 2 Prescalers

    Der Timer 2 besitzt einen eigenen Vorteiler der für den gewünschten Zweck dieser Zeitbasis auf einen Teiler-Faktor von 1024 eingestellt wird. Der Timer 2 Prescaler ist nicht frei programmierbar sondern ermöglicht nur Teiler-Faktoren von

    1 / 2 (PSC[3..0])

    gesetzt durch die PSC[3..0] Bits im Register TIM2_PSCR.

    Mit der gewählten Einstellung des Vorteilers ergibt sich ein CK_CNT von 16MHz / 512 = 31,250 kHz als Zählerfrequenz für den Timer 2. Dieser Frequenz von 31,250 kHz entspricht eine minimale Zeitauflösung von 1/31250 = 32µsec.


    Da als Ziel ein 100msec Interrupt vorgegeben ist muß das Auto-Reload Register des Timer 2 mit einem Wert von 100msec/32µsec = 3125   entspricht hexadezimal 0x0C35h,  geladen werden um die 100 msec zu erzielen.

    Der Zähler zählt damit von 0 bis 3125 da im Auto-Reload Register dieser Wert eingetragen ist. Durch den Gleichstand (match) von Zähler und Auto-Reload Register wird ein Zähler Reset durchgeführt.

    Die Zeitbasis soll mit möglichst wenig Software Unterstützung funktionieren, daher wird der Timer 2 als freilaufender Timer initialisiert und der Update Event (Überlauf) erzeugt einen Timer 2 Interrupt.

    Die Timer 2 Initialisierung

    In nachfolgendem Programm Listing sind die oben erarbeiteten Werte verwendet um den Timer 2 entsprechend zu konfigurieren.

  • Prescaler Einstellung auf 0x0Ah (Teiler-Faktor 512) in TIM2_PSCR
  • Auto-Reload Register auf 0x0C35h setzen (Teiler-Faktor 3125) in TIM2_ARRH(L)
  • ARPE Bit gesetzt in TIM2_CR1 (Shadow Register freigegeben - option)
  • CEN Bit gesetzt in TIM2_CR1 (Freigabe des Zählers)
  • UIE Bit gesetzt in TIM2_IER (Freigabe des Update Interrupts)

  • ;*******************************************************************************************

			  ; Initialisierung:  Timer 2                         (Zeitbasis @ 100ms mit Interrupt Freigabe)

			  ;*******************************************************************************************

			  	ld	A,#$0A		; Wert für das Prescaler Register 		(für Teilerfaktor 512)

			  	ld	TIM2_PSCR,A	; Lade das Prescaler Register 			(mit 0x0Ah)

			  					; Das TIM2_PSCR ist ein 8 Bit Register

			  	ld	A,#$0C		; Setze das “Auto Preload High Byte”  		(mit 0x0Ch))

			  	ld	TIM2_ARRH,A	; Lade das “Auto Preload High-Byte”		(mit 0x0Ch)

			  	ld	A,#$35		; Setze das “Auto Preload  Low Byte”  		(mit 0x35h))

			  					; der Wert 0x0C35h entspricht 3125 		(als Teiler)

			  	ld	TIM2_ARRL,A	; Lade das “Auto Preload Low-Byte” 		(mit 0x35h)

			  	ld	A,#$81		; Freigabe Wert für Auto Preload Shadow, Counter enable

			  	ld	TIM2_CR1,A	; Lade TIM2 Control Register 1 		(mit 0x81h)

			  	ld	A,#$01		; Enable Wert für Update Interrupt im TIM2_IER

			  	ld	TIM2_IER,A	; Lade TIM2 Interrupt Enable Register 		(mit 0x01h)

			  					; Timer 2 Interrupt wird freigegeben

    Nach der Initialisierung des Timer 2 wird nun alle 100 ms ein „Timer2 Overflow Interrupt“ erzeugt der auf den Interrupt Vector irq13 verzweigt und damit die Adresse „Timer2Interrupt“ als Einsprungadresse nutzt.

    Der Timer 2 Interrupt

    Wie beim STM8 üblich besteht jeder Interrupt Vector aus 4 Byte, wobei das erste Byte 0x82h den Maschinenbefehl „INT“ darstellt und die darauf folgenden drei Bytes eine lange 3 Byte Adresse (far) die vom  Assembler als relative Adresse „Timer2Interrupt.l“ geführt wird und erst bei einem Link-Vorgang während des „assemblierens“ eine absolute Adresse im Speicher für die Interrupt Subroutine anlegt.

    ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

				;          Interrupt Table

				;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

				segment 'vectit'

				dc.l {$82000000+main}					; reset

				dc.l {$82000000+NonHandledInterrupt}	; trap

				dc.l {$82000000+NonHandledInterrupt}	; irq0

				dc.l {$82000000+NonHandledInterrupt}	; irq1

				dc.l {$82000000+NonHandledInterrupt}	; irq2

				dc.l {$82000000+NonHandledInterrupt}	; irq3

				dc.l {$82000000+NonHandledInterrupt}	; irq4

				dc.l {$82000000+NonHandledInterrupt}	; irq5

				dc.l {$82000000+NonHandledInterrupt}	; irq6

				dc.l {$82000000+NonHandledInterrupt}	; irq7

				dc.l {$82000000+NonHandledInterrupt}	; irq8

				dc.l {$82000000+NonHandledInterrupt}	; irq9

				dc.l {$82000000+NonHandledInterrupt}	; irq10

				dc.l {$82000000+NonHandledInterrupt}	; irq11

				dc.l {$82000000+NonHandledInterrupt}	; irq12

				dc.l {$82000000+Timer2Interrupt}		; irq13	Overflow

				dc.l {$82000000+NonHandledInterrupt}	; irq14

				dc.l {$82000000+NonHandledInterrupt}	; irq15

				dc.l {$82000000+NopnHandledInterrupt}	; irq16

				dc.l {$82000000+NonHandledInterrupt}	; irq17

				dc.l {$82000000+NonHandledInterrupt}	; irq18

				dc.l {$82000000+NonHandledInterrupt}	; irq19

				dc.l {$82000000+NonHandledInterrupt}	; irq20

				dc.l {$82000000+NonHandledInterrupt}	; irq21

				dc.l {$82000000+NonHandledInterrupt}	; irq22

				dc.l {$82000000+NonHandledInterrupt}	; irq23

				dc.l {$82000000+NonHandledInterrupt}	; irq24

				dc.l {$82000000+NonHandledInterrupt}	; irq25

				dc.l {$82000000+NonHandledInterrupt}	; irq26

				dc.l {$82000000+NonHandledInterrupt}	; irq27

				dc.l {$82000000+NonHandledInterrupt}	; irq28

				dc.l {$82000000+NonHandledInterrupt}	; irq29

    Verzweigt der Prozessor durch den ausgelösten Hardware-Interrupt auf die Addresse „Timer2Interrupt.l“ ( „.l“ steht für eine „lange 3 Byte Adresse“), so findet er an dieser Stelle die Interrupt Subroutine für den Timer2 Interrupt.

    Timer 2 Interrupt Subroutine

    Die nachfolgend gelistete Interrupt Subroutine verwaltet die Informationen zur Übergabe in den Variablen TFLAG, SECFLAG und BITTEST.

    ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

				;      ISR	-	Timer 2 Interrupt

				;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~	

					interrupt	Timer2

				Timer2Interrupt.l				; Adresse der Timer 2 Interrupt Subroutine

					sim							; Interrupts werden global gesperren

					inc		TFLAG				; Inkrement Timer Flag (100ms)

					bres	TIM2_SR1,#0			; Interrupt Flag UIF im TIM2_SR1 zurücksetzen
	

					ld		A,#$0A				; Lade Referenzwert = 10 zum Vergleich

					cp		A,TFLAG				; Vergleiche TFLAG = 10 ?

					jrne	END_SEC				; Wenn TFLAG < 10 verzweige nach "END_SEC"

					inc		SECFLAG				; Andernfalls erhöhe den Wert von SECFLAG,

												; wird von der S/W-Anwendung gelöscht

					clr		A					; Accumulator mit “0x00h” laden

					ld		TFLAG,A				; TFLAG mit “0x00h” laden (zurücksetzen falls 10)
	

				END_SEC							; Sprungmarke: Ende einer Sekunde
	

					btjf	BITTEST,#0,SET_0	; Bittest: Verzweige nach SET_0,  falls Bit = 0 (false) 

					bres	PD_ODR,#0			; Setze das Bit 0 im PD_ODR zurück (“0”) (LED ausschalten *)

					bres	BITTEST,#0			; Setze das Bit 0 in BITTEST zurück (“0”)

					jra		TOGG_END			; Springe nach TOGG_END zum Ende der Interrupt Routine


				SET_0							; Sprungmarke: Setze GPIO-Bit und BITTEST aktiv


					bset	PD_ODR,#0			; Setze das Bit 0 im PD_ODR auf “1” (LED einschalten *)

					bset	BITTEST,#0			; Setze das Bit 0 in BITTEST auf “1”
	

				TOGG_END						; Sprungmarke: Verlasse die Interrupt Routine	


					rim							; aktiviere global alle Interrupts

					iret						; Beende die Interrupt Service routine

    Natürlich kann die Funktion dieser Subroutine an die Anforderungen angepasst werden.

    Wie bereits beschrieben ist „Timer2Interrupt.l“ die Einsprungadresse (Label) für die Timer2 Interrupt Routine.

    Als erste Aktion werden alle Interrupts global gesperrt um ein intensives „Nesting“ von Interrupts zu vermeiden. Dazu sollte die Interrupt Routine so kurz wie möglich sein.

    Im Folgenden wird das TFLAG inkrementiert und auf den Wert 10 getestet. TFLAG soll von 0 bis 9 zählen um das SECFLAG jede Sekunde zu inkrementieren. Mit diesen beiden Variablen stehen der Anwendung zwei Indikatoren zur Verfügung. Zum Einen ein 100ms Signal (TFLAG) das für schnelle Zeitabläufe innerhalb der Anwendung genutzt werden kann und ein 1 Sekunden Signal (SECFLAG) das für eine Uhrzeit oder für langsame zeitliche Effekte im Zusammenwirken mit einer Software-Applikation von Vorteil sein wird.

    Zur Visualsierung der Funktion dieser Zeitbasis wurde zusätzlich die Toggle-Funktion für ein GPIO oder eine LED implementiert.

    TOGG_END ist die Sprungmarke für das Beenden der Interrupt Subroutine. Ab diesem Punkt werden alle Interrupts global wieder freigegeben (rim) und schließlich erfolgt der Rücksprung (iret) in den normalen Kontext.

     Copyright Notiz

    Dieses Dokument sowie dessen Inhalt, insbesondere Texte, Fotografien und Grafiken, unterliegt dem Copyright (© 2017) und sind nur mit einer schriftlicher Zustimmung des Autors, Dipl.Ing.(FH) Franz Henkel zur vollständigen oder auszugsweisen Weiterverwendung  in Form  einer gedruckten oder elektronischen Kopie oder Replikation bzw. einer vollständigen oder auszugsweisen Bereitstellung des Inhalts in schriftlicher oder elektronischer Form, zu verwenden.